#include "blist.h"
#include "BST.h"



namespace STLData {

    long maxMem = 1000;
    bfs::path librTemp;

    blist::blist(const blist &other)
    {
        if (this != &other) {
            headerSize = other.headerSize;
            lineSize   = other.lineSize;
            bufferSize = other.bufferSize;
            numLines   = other.numLines;
            headerValue.copyStruct(other.headerValue);
            readBuffer.str(std::string());
            updtBuffer.str(std::string());
            appnBuffer.str(std::string());
            dtstFile.close();
//            other.dtstFile.close();
            bfs::copy_file(other.dtstPath, dtstPath, bfs::copy_option::overwrite_if_exists);
            dtstFile.open(dtstPath, std::fstream::in | std::fstream::out);
//            other.dtstFile.open(other.dtstPath, std::fstream::in | std::fstream::out);
            begin();
        }
    }

    blist::blist(std::string filename, std::string library) {

        char * input;
        char inputchar;
        std::string headerStruct;
        bfs::ofstream ofile;
        int i=0;

        dtstName = filename;
        dtstLibr = library;

        if (bfs::is_directory(dtstLibr)) {
            dtstPath = dtstLibr / dtstName;
        } else {
            libraryNotFound ex1;
            ex1.libraryPath=dtstLibr;
            throw ex1;
        }

        if (bfs::exists(dtstPath) && bfs::is_regular_file(dtstPath)) {
            dtstFile.open(dtstPath,
                    bfs::fstream::in | bfs::fstream::out | bfs::fstream::binary);
            if (dtstFile.fail()) {
                fileNotFound ex2;
                ex2.filePath = dtstPath;
                throw ex2;
            }

        } else {
            fileNotFound ex3;
            ex3.filePath = dtstPath;
            throw ex3;
        }


        dtstFile.seekg(0, std::ios::beg);
        dtstFile.seekp(0, std::ios::beg);

   //   Knowing Header length


        std::string encoded;
        std::string decoded;
        long *sz;

//        std::getline(dtstFile, encoded, '|');
        dtstFile.get(inputchar);
        encoded.clear();
        while (inputchar != '|') {
            encoded += inputchar;
            dtstFile.get(inputchar);
        }

        if (encoded.size() != 0) {
            decoded = base64_decode(encoded);
            sz = reinterpret_cast<long*>((char *)decoded.c_str());
            headerSize = *sz;
        }

        headerStruct.clear();

        dtstFile.seekg(0, std::ios::beg);
        input = new char[headerSize];
        dtstFile.get(input, headerSize+1, ',');
        headerStruct.assign(input);

        std::string fieldName;
        std::string fieldType;
        std::string lineTxt;
        for (i=9; i<static_cast<int>(headerStruct.size());i++) {
            if (headerStruct[i] == '|') {
                int l = fieldType.size() - 1;
                std::string strl;
                if (l>0) {
                    strl = fieldType.substr(1, fieldType.size() - 1);
                    l = StringToNumber<int>(strl);
                } else {
                    l = 0;
                }
                headerValue.createField(fieldName, fieldType[0], l);
                fieldName.clear();
                fieldType.clear();
            } else if (headerStruct[i] == ' ') {
                fieldName = fieldType;
                fieldType.clear();
            } else {
                fieldType+=headerStruct[i];
            }
        }

        lineTxt = codeHeader();
        lineSize = lineTxt.size();
        presBufferPos = dtstFile.tellg();

 //       while (input != '\n') {
 //           lineTxt += input;
 //           dtstFile >> input;
 //       }
        numLines = maxMem / lineSize;
        numLines = numLines / 2;
        bufferSize = numLines * lineSize;
        readLine();
    }

    blist::blist(dataLine & variables, std::string &dataSetName,
                std::string &library) {

        std::string headerStruct;
        std::vector<std::string> headerDefs;
        bfs::ofstream ofile;

        dtstName = dataSetName;
        dtstLibr = library;

        if (bfs::is_directory(dtstLibr)) {
            dtstPath = dtstLibr / dtstName;
        } else {
            libraryNotFound ex1;
            ex1.libraryPath=dtstLibr;
            throw ex1;
        }
        ofile.open(dtstPath);
        ofile.close();
        dtstFile.open(dtstPath, std::fstream::in | std::fstream::out | bfs::fstream::binary);
//        if (dtstFile.is_open()) {
        dtstFile.seekg(0, std::ios::beg);
        dtstFile.seekp(0, std::ios::beg);
//        }
        headerStruct.clear();
        headerValue.copyStruct(variables);
        dataLine::iterator it;
        headerStruct = "|";
        for (it=headerValue.begin();it!=headerValue.end();it++) {
            headerStruct += it->key();
            headerStruct += ' ';
            headerStruct += it->type();
            if (it->type()=='T') {
                std::ostringstream hdlength;
                hdlength << it->length();
                headerStruct += hdlength.str();
                hdlength.str(std::string());
            }
            headerStruct += '|';
        }
        headerStruct += '\n';
        headerSize = headerStruct.size() + 8;
        std::string encodingField;
        encodingField = base64_encode(reinterpret_cast<const unsigned char*>(&headerSize), sizeof(long));
        headerStruct = encodingField + headerStruct;
        dtstFile.write(headerStruct.c_str(), headerSize);
//        if (dtstFile.is_open()) {
        dtstFile.seekg(0, std::ios::beg);
        dtstFile.seekp(0, std::ios::beg);
        std::string lineTest;
        lineTest = codeHeader();
        lineSize = (int) lineTest.size();
        numLines = maxMem / lineSize;
        numLines = numLines / 2;
        bufferSize = numLines * lineSize;
        dtstFile.seekp(headerSize,std::ios::beg);
        presBufferPos = dtstFile.tellp();
    }

    blist::~blist() {
        //Flush both append buffer and update buffer
        if ((updtBuffer.str().size()>0)) {
            flushBuffer();
         }
        if (appnBuffer.str().size()>0) {
            appndBuffer();
        }
        dtstFile.close();
        if (dtstLibr==librTemp) {
            bfs::remove(dtstPath);
        }
    }

    bool blist::readLine(){

        if (lineCoded.size()>0) {
            updtBuffer.write(lineCoded.c_str(), lineCoded.size());
            lineCoded.clear();
        }

        // Read buffer empty
        if (readBuffer.str().size()==0) {

            if (updtBuffer.str().size()>0) {
                flushBuffer();
            }
            if (!fillBuffer(presBufferPos))
                return false;
        }

        std::getline(readBuffer, lineCoded);
        lineCoded += '\n';
        decodeLine(lineCoded);
        return true;

    }

    bool blist::modifyLine() {

        lineCoded = codeHeader();

        return true;
    }

    bool blist::appendLine() {

        std::string codedLine;

        codedLine = codeHeader();

        appnBuffer.write(codedLine.c_str(), codedLine.size());

        if (static_cast<int>(appnBuffer.str().size())==bufferSize) {
                dtstFile.seekp(0, std::ios::end);
                dtstFile.write(appnBuffer.str().c_str(), appnBuffer.str().size());
                appnBuffer.str(std::string());
        }

        return true;
    }

    bool blist::insertLine() {

        std::streampos nextPos;

        updtBuffer.write(lineCoded.c_str(), lineCoded.size());
        codeHeader();
        updtBuffer.write(lineCoded.c_str(), lineCoded.size());
        updtBuffer.write(readBuffer.str().c_str(), readBuffer.str().size());
        nextPos = presBufferPos + static_cast<std::streamoff>(bufferSize);
        while (!dtstFile.eof()) {
            fillBuffer(nextPos);
            nextPos = nextPos + static_cast<std::streamoff>(dtstFile.gcount());
            flushBuffer();
            updtBuffer.write(readBuffer.str().c_str(), readBuffer.str().size());
        }
        return true;

    }

    bool blist::begin() {

        // Flush buffer data
        if (lineCoded.size()>0) {
            updtBuffer.write(lineCoded.c_str(), lineCoded.size());
            lineCoded.clear();
        }
        if (updtBuffer.str().size()>0) {
            flushBuffer();
        }
        // Clear input buffer
        readBuffer.str(std::string());

        // Go to begin of file

        dtstFile.seekg(headerSize, std::ios::beg);
        presBufferPos = dtstFile.tellg();

        // Read line
        return readLine();

    }

    blist & blist::operator++() {
        readLine();
        return *this;
    }

    blist & blist::operator++(int) {
        ++(*this);
        return *this;
    }

    bool blist::readIndex(int index) {

        std::streampos blockNumber;
        std::streampos init;
        std::streampos endFile;
        std::streampos posLine;
        int blockLine;

        dtstFile.seekp(headerSize, bfs::fstream::beg);
        init = dtstFile.tellp();
        dtstFile.seekp(0, bfs::fstream::end);
        endFile = dtstFile.tellp();

        blockNumber = init + static_cast<std::streamoff>(((index+1)*lineSize/bufferSize)*bufferSize);
        posLine = init + static_cast<std::streamoff>((index+1)*lineSize);
        blockLine = ((index+1)*lineSize) % bufferSize;

        if (posLine<endFile) {
            flushBuffer();
            fillBuffer(init);
            for (int i = 1;i<blockLine;i++) {
                readLine();
            }
            return true;
        } else {
            return false;
        }
    }

    blist & blist::operator=(blist &other)
    {
        if (this == &other)
            return *this;

        flushBuffer();
        appndBuffer();
        other.flushBuffer();
        other.appndBuffer();
        headerSize = other.headerSize;
        lineSize   = other.lineSize;
        bufferSize = other.bufferSize;
        numLines   = other.numLines;
        headerValue.copyStruct(other.headerValue);
        readBuffer.str(std::string());
        updtBuffer.str(std::string());
        appnBuffer.str(std::string());
        dtstFile.close();
        other.dtstFile.close();
        bfs::copy_file(other.dtstPath, dtstPath, bfs::copy_option::overwrite_if_exists);
        dtstFile.open(dtstPath, std::fstream::in | std::fstream::out);
        other.dtstFile.open(other.dtstPath, std::fstream::in | std::fstream::out);
        begin();
        return *this;
    }

    blist & blist::operator+=( blist &rhs)
    {
        char * buffer;

        if (headerValue.compStruct(rhs.headerValue)) {
            flushBuffer();
            appndBuffer();
            rhs.flushBuffer();
            rhs.appndBuffer();

            dtstFile.seekp(0, std::ios::end);
            rhs.dtstFile.seekg(rhs.headerSize, std::ios::beg);
            while (!(rhs.dtstFile.eof())) {
                buffer = (char*)malloc(bufferSize*sizeof(char));
                rhs.dtstFile.read(buffer,bufferSize);
                dtstFile.write(buffer,rhs.dtstFile.gcount());
            }
            return *this;
        } else {
            return *this;
        }
    }

    const blist blist::operator+( blist &other) const {
        blist result(*this);     // Make a copy of myself.  Same as MyClass result(*this);
        result += other;            // Use += to add other to the copy.
        return result;              // All done!
    }

    bool blist::decodeLine(const std::string lineString) {

        std::string encoded;
        std::string decoded;
        std::istringstream iss;
        double *d;
        long *l;
        time_t *t;

        dataLine::iterator it = headerValue.begin();

        iss.str(lineString);
        std::getline(iss, encoded, '|');
        while(std::getline(iss, encoded, '|')) {
            if (encoded.size() != 0) {
                decoded = base64_decode(encoded);
                switch ((*it).type()) {
                    case 'T':
                        (*it) = decoded;
                        break;
                    case 'R':
                        d = reinterpret_cast<double*>((char *)decoded.c_str());
                        (*it) = *d;
                        break;
                    case 'I':
                        l = reinterpret_cast<long*>((char *)decoded.c_str());
                        (*it) = *l;
                        break;
                    case 'D':
                        t = reinterpret_cast<time_t*>((char *)decoded.c_str());
                        (*it) = *t;
                        break;
                }
            }
            it++;
            encoded.clear();
            decoded.clear();
        }
        return true;
    }

    std::string blist::codeHeader() {

        std::string fileEncoded, encodingField;
        dataLine::iterator it;

        fileEncoded += '|';
        for (it=headerValue.begin();it!=headerValue.end();it++) {
            std::string buff;
            encodingField.clear();
            switch (it->type()) {
                case 'T':
                    buff = (char *)(*it);
                    while (static_cast<int>(buff.size())<it->length()) {
                        buff += ' ';
                    }
                    encodingField = base64_encode(reinterpret_cast<const unsigned char*>(buff.c_str()), buff.length());
                    break;
                case 'R':
                    double valR;
                    valR = static_cast<double>(*it);
                    encodingField = base64_encode(reinterpret_cast<const unsigned char*>(&valR), sizeof(double));
                    break;
                case 'I':
                    long valI;
                    valI = static_cast<long>(*it);
                    encodingField = base64_encode(reinterpret_cast<const unsigned char*>(&valI), sizeof(long));
                    break;
                case 'D':
                    time_t valD;
                    valD = static_cast<time_t>(*it);
                    encodingField = base64_encode(reinterpret_cast<const unsigned char*>(&valD), sizeof(time_t));
                    break;
            }
            if (fileEncoded.size()>0) {
                fileEncoded += encodingField;
                fileEncoded += '|';
            }
        }
        while (static_cast<int>(fileEncoded.size()) < lineSize -1) {
            fileEncoded += ' ';
        }
  /*      if (static_cast<int>(fileEncoded.size()) > lineSize -1) {
            fileEncoded = fileEncoded.substr(0, lineSize -2);
            fileEncoded += '|';
        }*/
        fileEncoded += '\n';

        return fileEncoded;
    }

    bool blist::end() {

        if (appnBuffer.str().size()>0)
            appndBuffer();
         if (updtBuffer.str().size()>0)
            flushBuffer();

        readBuffer.str(std::string());
        lineCoded.clear();
        dtstFile.seekg(0, std::ios::end);
        presBufferPos = dtstFile.tellg();
        return true;

    }

    bool blist::sort(std::vector<std::string> sortFields, std::vector<std::string> sortOrder) {

        int max = 10000;
        BST tree;
        BNode::Comparable buffer;
        std::vector<std::fstream *> files;
        std::ostringstream convert;

        int i;

        std::string pattern("/tmp/sort");
        std::string totalName;

        flushBuffer();
        appndBuffer();

        for (i=1;i<static_cast<int>(sortFields.size());i++) {
            buffer.fields.createField(sortFields[i],
                headerValue[sortFields[i]].type(), headerValue[sortFields[i]].length());
        }
        begin();
        buffer.fields.corresponding(headerValue);
        buffer.fileLine = lineCoded;
        tree.setComp(sortOrder);
        tree.insert(buffer);
        int j = 0;
        bool repeatLoop = true;
        while (repeatLoop) {
            for (i=2;i<max;i++) {
                if (readLine()) {
                    buffer.fields.corresponding(headerValue);
                    buffer.fileLine = lineCoded;
                    tree.insert(buffer);
                } else {
                    repeatLoop = false;
                    break;
                }
            }

            convert.str("");
            convert.fill('0');
            convert.width(4);
            convert << j;
            totalName = pattern + convert.str();
            std::fstream * ptr;
            ptr = new std::fstream;

            ptr->open(totalName.c_str(), std::fstream::in | std::fstream::out);
            tree.setFile(*(ptr));
            tree.inorderTree();
            files.push_back(ptr);
            j = j+1;
            tree.makeEmpty();

        }

        char * input;
        std::string lineRead;

        input = (char *) malloc(lineSize*sizeof(char));

        for (i=0; i<=j; i++) {
            files[i]->getline(input, lineSize*sizeof(char));
            lineRead.assign(input);
            decodeLine(lineRead);
            buffer.fields.corresponding(headerValue);
            buffer.fileLine = lineCoded;
            buffer.filenum = i;
            tree.insert(buffer);
        }

        bfs::fstream out;
        totalName = "/tmp/" + dtstName + "9999";
        bfs::path tempFile(totalName);
        out.open(tempFile, std::fstream::out | std::fstream::binary);
        out.seekg(0, std::ios::beg);
        out.seekp(0, std::ios::beg);
        dtstFile.seekg(0, std::ios::beg);
        dtstFile.get(input, headerSize+1, ',');
        lineRead.assign(input);
        out << lineRead;
        buffer = tree.findMin();
        while (buffer.fileLine!="NaN") {
            out << buffer.fileLine << std::endl;
            if (!(files[buffer.filenum]->eof())) {
                std::getline(*files[buffer.filenum], lineRead);
                files[buffer.filenum]->getline(input, lineSize*sizeof(char));
                lineRead.assign(input);
                decodeLine(lineRead);
                buffer.fields.corresponding(headerValue);
                buffer.fileLine = lineCoded;
                buffer.filenum = buffer.filenum;
                tree.insert(buffer);
            }
            buffer = tree.findMin();
        }
        for (unsigned int z = 0; z<files.size();z++) {
            files[z]->close();
            delete files[z];
        }

        out.close();
        dtstFile.close();
        bfs::copy_file(tempFile, dtstPath, bfs::copy_option::overwrite_if_exists);
        dtstFile.open(dtstPath, std::fstream::in | std::fstream::out | std::fstream::binary);
        begin();

        return true;

    }

    bool blist::flushBuffer() {

        if (presBufferPos!=dtstFile.tellp())
            dtstFile.seekp(presBufferPos);

        std::string tst = updtBuffer.str();
        unsigned int lng = updtBuffer.str().size();
        dtstFile.write(updtBuffer.str().c_str(), updtBuffer.str().size());
        dtstFile.flush();
        updtBuffer.str(std::string());
        presBufferPos = dtstFile.tellp();
        return true;

    }
    bool blist::fillBuffer(std::streampos pos) {

        char * buffer = new char[bufferSize];

//        buffer = (char *) malloc(bufferSize*sizeof(char));

        if (pos!=dtstFile.tellg())
            dtstFile.seekg(pos);
        presBufferPos = pos;
        dtstFile.read(buffer,bufferSize);
        int size = dtstFile.gcount();
        if (dtstFile.gcount() != 0) {
            readBuffer.rdbuf()->pubsetbuf(buffer,dtstFile.gcount());
            return true;
        } else {
            if (appnBuffer.str().size() > 0) {
                appndBuffer();
                dtstFile.read(buffer,bufferSize);
                if (dtstFile.gcount() != 0) {
                    readBuffer.rdbuf()->pubsetbuf(buffer,dtstFile.gcount());
                    return true;
                } else {
                    return false;
                }
            } else {
                return false;
            }
        }

    }

    bool blist::appndBuffer() {
        dtstFile.seekp(0, std::ios::end);
        dtstFile.write(appnBuffer.str().c_str(), appnBuffer.str().size());
        dtstFile.flush();
        appnBuffer.str(std::string());
        return true;
    }

}
